import "babel-polyfill";
import Queue from "../data-structure/queue";

export var enumerate = function*(start = 0) {
    for (let x of this) {
        yield [start++, x];
    }
};

export var forEach = function(callback) {
    for (let x of this) {
        callback(x);
    }
};

export var map = function*(callback) {
    for (let x of this) {
        yield callback(x);
    }
};

export var filter = function*(callback) {
    for (let x of this) {
        if (callback(x)) {
            yield x;
        }
    }
};

export var reduce = function(callback) {
    var that = this[Symbol.iterator]();
    var result = arguments.length > 1 ? arguments[1] : that.next().value;
    for (let x of that) {
        result = callback(result, x);
    }
    return result;
};

export var zip = function*(...iterators) {
    if (iterators.length === 0) {
        return;
    }
    for (let i = 0; i < iterators.length; ++i) {
        iterators[i] = iterators[i][Symbol.iterator]();
    }
    while (true) {
        let args = [];
        for (let iterator of iterators) {
            let next = iterator.next();
            if (next.done) {
                return;
            }
            args.push(next.value);
        }
        yield args;
    }
};

export var some = function(callback) {
    for (let x of this) {
        if (callback(x)) {
            return true;
        }
    }
    return false;
};

export var every = function(callback) {
    for (let x of this) {
        if (!callback(x)) {
            return false;
        }
    }
    return true;
};

export var tee = function(n = 2) {
    var that = this[Symbol.iterator]();
    var queue_list = [...range(n)::map(() => new Queue())];
    return queue_list.map(function*(queue) {
        while (true) {
            if (queue.length === 0) {
                let next = that.next();
                if (next.done) {
                    return;
                }
                let value = next.value;
                queue_list.forEach(q => q.push(value));
            }
            yield queue.pop();
        }
    });
};

export var chain = function*() {
    for (let iterator of arguments) {
        yield* iterator;
    }
};

export var repeat = function*(value, n = Infinity) {
    for (let i = 0; i < n; ++i) {
        yield value;
    }
};

export var accumulate = function*(callback) {
    var that = this[Symbol.iterator]();
    if (arguments.length > 1) {
        var result = arguments[1];
    } else {
        var result = that.next().value;
        yield result;
    }
    for (let x of that) {
        result = callback(result, x);
        yield result;
    }
};

export var range = function*(start = 0, stop = Infinity, step = 1) {
    if (arguments.length == 1) {
        start = 0;
        stop = arguments[0];
    }
    var pred = step > 0 ? x => x < stop : x => x > stop;
    for (let i = start; pred(i); i += step) {
        yield i;
    }
};

export var slice = function*(start = 0, stop = Infinity, step = 1) {
    var that = this[Symbol.iterator]();
    for (let i = 0; i < start; ++i) {
        that.next();
    }
    for (let i = start; i < stop; i += step) {
        let next = that.next();
        if (next.done) {
            return;
        }
        yield next.value;
        for (let j = 1; j < step; ++j) {
            if (that.next().done) {
                return;
            }
        }
    }
};

export var partition = function(callback) {
    var [t1, t2] = this::tee();
    return [t1::filter(x => !callback(x)), t2::filter(callback)];
};
